package com.linkoog.devtools.websocket.toolwindow;

import com.intellij.icons.AllIcons;
import com.intellij.ide.util.ElementsChooser;
import com.intellij.lang.Language;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.actionSystem.ActionManager;
import com.intellij.openapi.actionSystem.ActionPlaces;
import com.intellij.openapi.actionSystem.ActionToolbar;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.DefaultActionGroup;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.command.WriteCommandAction;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.fileEditor.FileDocumentManager;
import com.intellij.openapi.fileEditor.FileEditor;
import com.intellij.openapi.fileEditor.impl.text.TextEditorProvider;
import com.intellij.openapi.fileTypes.PlainTextLanguage;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.ui.Splitter;
import com.intellij.openapi.ui.popup.JBPopupFactory;
import com.intellij.openapi.wm.IdeFocusManager;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiFileFactory;
import com.intellij.ui.GuiUtils;
import com.intellij.ui.HoverHyperlinkLabel;
import com.intellij.ui.HyperlinkLabel;
import com.intellij.ui.JBColor;
import com.intellij.ui.ToolbarDecorator;
import com.intellij.ui.components.JBTextField;
import com.intellij.ui.table.JBTable;
import com.intellij.util.ui.ListTableModel;
import com.linkoog.devtools.utils.StringUtils;
import com.linkoog.devtools.websocket.model.WSFrameData;
import com.linkoog.devtools.websocket.toolwindow.left.FilterPopupUtil;
import com.linkoog.devtools.websocket.toolwindow.left.FilteringTableModel;
import com.linkoog.devtools.websocket.toolwindow.left.WSDataTableUtil;
import com.linkoog.devtools.websocket.toolwindow.right.WebsocketHeaderDialog;
import com.linkoog.devtools.websocket.wsclient.jdk.JdkWSClient;
import org.jetbrains.annotations.NotNull;

import javax.swing.*;
import javax.swing.border.LineBorder;
import javax.swing.event.HyperlinkEvent;
import javax.swing.event.HyperlinkListener;
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.net.http.WebSocket;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletionStage;

public class WebsocketContentPanel implements Disposable {

    private final Project project;
    private JPanel myMainPanel;
    private JComboBox schemeComboBox;
    private JTextField urlTextField;
    private JButton connectButton;
    private JLabel statusLabel;


    private JPanel contentPanel;

    private JPanel leftPanel;
    private JBTable table;
    private FilteringTableModel<WSFrameData> model;


    private JPanel rightPanel;
    private JPanel rightToolbarPanel;
    private JPanel sendPanel;
    private JButton sendButton;
    private HyperlinkLabel hyperlinkLabel;

    private FileEditor textEditor;

    private JdkWSClient jdkWSClient;


    private final Map<String, String> cookiesMap = new LinkedHashMap<>();
    private final Map<String, String> headersMap = new LinkedHashMap<>();





    public WebsocketContentPanel(@NotNull Project project) {
        this.project = project;
        GuiUtils.replaceJSplitPaneWithIDEASplitter(myMainPanel);
        Splitter splitter = (Splitter)contentPanel.getComponent(0);
        splitter.setDividerPositionStrategy(Splitter.DividerPositionStrategy.KEEP_FIRST_SIZE);
        splitter.setProportion(0.56F);
        splitter.setShowDividerControls(true);
        //splitter.setHonorComponentsMinimumSize(true);
        init();
    }

    @Override
    public void dispose() {
        if (jdkWSClient != null){
            jdkWSClient.close();
        }
    }


    private void init() {
        initLeftPanel();
        initRightPanel();

        schemeComboBox.addItem("ws://");
        schemeComboBox.addItem("wss://");

        final Dimension dimension = new Dimension(120, -1);
        urlTextField.setSize(dimension);
        urlTextField.setMaximumSize(dimension);
        urlTextField.setPreferredSize(dimension);
        urlTextField.setText("127.0.0.1:8080/ws");



        hyperlinkLabel.setHyperlinkText("Headers");
        hyperlinkLabel.addMouseListener(new MouseAdapter() {
            @Override
            public void mouseClicked(MouseEvent e) {
                WebsocketHeaderDialog dialog = new WebsocketHeaderDialog(project, cookiesMap, headersMap, cookies -> {
                    cookiesMap.clear();
                    cookiesMap.putAll(cookies);
                }, headers -> {
                    headersMap.clear();
                    headersMap.putAll(headers);
                });
                dialog.show();
            }
        });

        connectButton.setText("");
        connectButton.setIcon(AllIcons.Actions.Execute);
        connectButton.setMultiClickThreshhold(400);
        connectButton.addActionListener(e -> {
            if (jdkWSClient == null){
                String scheme = (String) schemeComboBox.getSelectedItem();
                String url = trimPerfix(urlTextField.getText());
                if (StringUtils.isBlank(url)){
                    Messages.showErrorDialog(myMainPanel, "请选择设置url连接地址");
                    return;
                }
                connectButton.setEnabled(false);
                createJdkWSClient(scheme + url, cookiesMap, headersMap);
            } else {
                setToUnConnectStatus();
            }
        });

        statusLabel.setText("Not Connect");
        statusLabel.setForeground(JBColor.RED);

        sendButton.setEnabled(false);
        sendButton.addActionListener(e -> {
            final String message = getStringFromEditor();
            if (StringUtils.isBlank(message)){
                return;
            }

            if (jdkWSClient != null){
                WSFrameData data = new WSFrameData(0, new Date(), message);
                ListTableModel<WSFrameData> originalModel = (ListTableModel<WSFrameData>)model.getOriginalModel();
                originalModel.insertRow(0, data);

                IdeFocusManager.getInstance(project).getFocusTargetFor(sendPanel);
                setEditorDoc("");
                ApplicationManager.getApplication().invokeLater(() -> {
                    jdkWSClient.send(message);
                });
            }
        });
    }

    private void createJdkWSClient(String url, Map<String, String> cookies, Map<String, String> headers){
        ApplicationManager.getApplication().invokeLater(() -> {
            try {
                JdkWSClient jdkWSClient = new JdkWSClient();
                boolean connFlag = jdkWSClient.connect(url, cookies, headers, new WebSocket.Listener() {
                    @Override
                    public void onOpen(WebSocket webSocket) {
                        setToConnectStatus();
                        WebsocketContentPanel.this.jdkWSClient = jdkWSClient;
                        WebSocket.Listener.super.onOpen(webSocket);
                    }

                    @Override
                    public CompletionStage<?> onText(WebSocket webSocket, CharSequence data, boolean last) {
                        SwingUtilities.invokeLater(() -> {
                            WSFrameData frame = new WSFrameData(1, new Date(), data.toString());
                            ListTableModel<WSFrameData> originalModel = (ListTableModel<WSFrameData>)model.getOriginalModel();
                            originalModel.insertRow(0, frame);
                        });
                        return WebSocket.Listener.super.onText(webSocket, data, last);
                    }

                    @Override
                    public CompletionStage<?> onClose(WebSocket webSocket, int statusCode, String reason) {
                        setToUnConnectStatus();
                        return WebSocket.Listener.super.onClose(webSocket, statusCode, reason);
                    }

                    @Override
                    public void onError(WebSocket webSocket, Throwable error) {
                        setToUnConnectStatus();
                        WebSocket.Listener.super.onError(webSocket, error);
                    }
                });

                if (! connFlag) {
                   ApplicationManager.getApplication().invokeLater(() -> {
                       setToUnConnectStatus();
                       Messages.showErrorDialog(myMainPanel, "连接websocket失败");
                   });
                }
            }catch (Exception exception){
                setToUnConnectStatus();
            }
        });
    }

    private void setToConnectStatus(){
        SwingUtilities.invokeLater(() -> {
            schemeComboBox.setEnabled(false);
            urlTextField.setEnabled(false);

            connectButton.setIcon(AllIcons.Actions.Suspend);
            connectButton.setEnabled(true);
            connectButton.updateUI();
            sendButton.setEnabled(true);

            statusLabel.setOpaque(true);
            statusLabel.setText("success");
            statusLabel.setForeground(JBColor.GREEN);
            IdeFocusManager.getInstance(project).getFocusTargetFor(sendPanel);
        });
    }

    private void setToUnConnectStatus(){
        ApplicationManager.getApplication().invokeLater(() -> {
            if (jdkWSClient != null){
                jdkWSClient.close();
                jdkWSClient = null;
            }

            SwingUtilities.invokeLater(() -> {
                schemeComboBox.setEnabled(true);
                urlTextField.setEnabled(true);

                connectButton.setEnabled(true);
                connectButton.setIcon(AllIcons.Actions.Execute);
                sendButton.setEnabled(false);

                statusLabel.setText("Not Connect");
                statusLabel.setForeground(JBColor.RED);
            });
        });
    }



    private void initLeftPanel(){
        table = WSDataTableUtil.initLeft_Table(project);
        model = (FilteringTableModel<WSFrameData>)table.getModel();

        ToolbarDecorator toolbarDecorator = ToolbarDecorator.createDecorator(table);
        toolbarDecorator.disableAddAction();
        toolbarDecorator.disableRemoveAction();
        toolbarDecorator.disableUpDownActions();

        ElementsChooser<String> chooser = FilterPopupUtil.createElementsChooser();
        chooser.addElementsMarkListener((ElementsChooser.ElementsMarkListener<String>) (element, isMarked) -> {
            model.refilter();
        });

        toolbarDecorator.addExtraAction(new AnAction(() ->"Filter", AllIcons.General.Filter) {
            @Override
            public void actionPerformed(@NotNull AnActionEvent e) {
                JBPopupFactory.getInstance().createComponentPopupBuilder(chooser,null)
                        .setRequestFocus(false)
                        .setFocusable(false)
                        .setResizable(true)
                        .createPopup()
                        .showUnderneathOf(e.getInputEvent().getComponent());

            }
        });

        SearchTextFieldAction searchTextFieldAction;
        toolbarDecorator.addExtraAction((searchTextFieldAction = new SearchTextFieldAction("search","search",null, 225){
            @Override
            public void perform() {
                model.refilter();
            }
        }));


        leftPanel.setSize(new Dimension( 780,-1));
        leftPanel.setPreferredSize(new Dimension( 780,-1));
        leftPanel.setMinimumSize(new Dimension( 260,-1));

        leftPanel.add(toolbarDecorator.createPanel(), SwingConstants.CENTER);


        model.setFilter(wsFrameData -> {
            String wsFrameDataType = wsFrameData.getType() == 1 ? FilterPopupUtil.INBOUND: FilterPopupUtil.OUTBOUND;
            final List<String> markedElements = chooser.getMarkedElements();
            if (! markedElements.contains(wsFrameDataType)) return false;

            final String searchText = searchTextFieldAction.getSearchText();
            if (StringUtils.isBlank(searchText)){
                return true;
            }

            final String data = wsFrameData.getData();
            return data != null && data.contains(searchText);
        });
    }

    private void initRightPanel(){
        rightPanel.setBorder(new LineBorder(JBColor.LIGHT_GRAY));

        ActionManager actionManager = ActionManager.getInstance();
        DefaultActionGroup actionGroup = new DefaultActionGroup();
        actionGroup.addAction(new AnAction(() ->"Filter", AllIcons.General.Filter) {
            @Override
            public void actionPerformed(@NotNull AnActionEvent e) {

            }
        });

        ActionToolbar leftToolbar = actionManager.createActionToolbar(ActionPlaces.TOOLBAR, actionGroup, true);
        leftToolbar.setTargetComponent(rightToolbarPanel);
        rightToolbarPanel.add(leftToolbar.getComponent(), BorderLayout.CENTER);
        rightToolbarPanel.setBorder(BorderFactory.createMatteBorder(0, 0, 1, 0, JBColor.LIGHT_GRAY));
        initSendPanel();

    }

    private void initSendPanel() {
        textEditor = createEditor(PlainTextLanguage.INSTANCE.getID(), null);
        final JComponent component = textEditor.getComponent();
        component.setBorder(BorderFactory.createEmptyBorder());
        sendPanel.add(component, BorderLayout.CENTER);
    }

    /**
     * set content for editor
     */
    private void setEditorDoc(String text) {
        WriteCommandAction.runWriteCommandAction(project, () -> {
            Document doc = getDocumentFromEditor(textEditor);
            doc.deleteString(0, doc.getTextLength());
            doc.setText(text == null ? "" : text.replaceAll("\\r", ""));
        });
    }

    private String getStringFromEditor() {
        Document doc = getDocumentFromEditor(textEditor);
        return doc.getText();
    }

    private Document getDocumentFromEditor(FileEditor editor) {
        return FileDocumentManager.getInstance().getCachedDocument(editor.getFile());
    }

    /**
     * create editor with specified language and content
     */
    private FileEditor createEditor(String languageId, String text) {
        if (StringUtils.isEmpty(text)) {
            text = "";
        }

        Language language = Language.findLanguageByID(languageId);
        PsiFile psiFile = PsiFileFactory.getInstance(project).createFileFromText(language, text);
        FileEditor editor = TextEditorProvider.getInstance().createEditor(project, psiFile.getVirtualFile());
        return editor;
    }

    public JPanel getMyMainPanel() {
        return myMainPanel;
    }

    private void createUIComponents() {
        JBTextField jbTextField = new JBTextField();
        jbTextField.getEmptyText().setText("ip:port");
        jbTextField.setPreferredSize(new Dimension(460, -1));
        urlTextField = jbTextField;
    }

    private String trimPerfix(String url){
        if (url != null){
            url = url.trim();
            if (url.startsWith("ws://")){
                url = url.substring(5);
            }

            if (url.startsWith("wss://")){
                url = url.substring(6);
            }
        }
        return url;
    }



}
